/* 
 * Enterprise Library Extensions - DI + ORM.
 * Copyright (C) 2006
 *
 * GNU General Public License
 *
 * This program is free software; you can redistribute 
 * it and/or modify it under the terms of the GNU General Public License 
 * as published by the Free Software Foundation; either version 2 of the 
 * License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful, but 
 * WITHOUT ANY WARRANTY; without even the implied warranty of 
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 
 * See the GNU General Public License for more details.
 */
using System;
using System.Collections;
using System.Collections.Specialized;
using System.Runtime.Remoting.Messaging;
using System.Runtime.Remoting.Contexts;
using System.Runtime.Remoting.Activation;
using System.Reflection;

using ELE.EnterpriseLibrary.Aspects.Attributes;
using ELE.EnterpriseLibrary.Naming;

namespace ELE.EnterpriseLibrary.Aspects.Sinks 
{

	/// <summary>
	/// Message sink that is used to intercept all method invocations on a context-bound object 
	/// only if the method has been annotated with the custom attribute, Aspects.Attributes.LogMethodAttribute
	/// An instance of this sink is added as a server sink into the invocation chain by Aspects.Properties.LogProperty
	/// </summary>
	public class AspectServerSink  : IMessageSink 
  {
		private IMessageSink next;
    private Hashtable dict;
    /// <summary>
    /// Constructor takes the next sink in the chain
    /// </summary>
    /// <param name="next"></param>
    public AspectServerSink (IMessageSink next):this(next, null) 
    { 
    }
    /// <summary>
    /// Constructor that uses a config file to configure the aspects
    /// </summary>
    /// <param name="next"></param>
    /// <param name="configFile"></param>
		public AspectServerSink (IMessageSink next, string configFile) 
    { 
      this.next = next;
      if(configFile != null)
      {
        AspectsTable aspects = AspectsTable.Read(configFile);
        dict = aspects.Aspects;
      }
    }
    /// <summary>
    /// This gets called on interception of the object method call
    /// </summary>
    /// <param name="msg"></param>
    /// <returns></returns>
		public IMessage SyncProcessMessage(IMessage msg) 
    {
      AspectConfigurationList confs = null;
			if (dict != null && msg is IMethodMessage) 
      {
        confs = (AspectConfigurationList) dict[((IMethodMessage) msg).MethodBase.Name];
			}
      if(confs != null)
      {
        msg = PreInjectAspect(msg as IMethodMessage, confs);
      }
			msg = next.SyncProcessMessage (msg);
      if( confs != null)
      {
        msg = PostInjectAspect(msg as IMethodMessage, confs);
      }
      return msg;
		}
    /// <summary>
    /// To get the next sink in the chain
    /// </summary>
		public IMessageSink NextSink { get {return this.next; } }
    /// <summary>
    /// Handle asynchronous calls
    /// </summary>
    /// <param name="msg"></param>
    /// <param name="replySink"></param>
    /// <returns></returns>
		public IMessageCtrl AsyncProcessMessage(IMessage msg, IMessageSink replySink) 
    {     
      /*Handle the asynchronous call, then:*/
      return next.AsyncProcessMessage(msg,replySink);
		}

    private IMessage PreInjectAspect (IMethodMessage methodMsg, AspectConfigurationList confs) 
    {
      foreach(AspectConfiguration conf in confs)
      {
        if(conf.JoinPoint == JoinPoint.Pre)
        {
          methodMsg = Execute(methodMsg, conf);
        }
      }
      return methodMsg;
    }

    private IMessage PostInjectAspect (IMethodMessage methodMsg, AspectConfigurationList confs) 
    {
      foreach(AspectConfiguration conf in confs)
      {
        if(conf.JoinPoint == JoinPoint.Post)
        {
          methodMsg = Execute(methodMsg, conf);
        }
      }
      return methodMsg;
    }
    private object CreateObjectForSink(string name)
    {
      if (name.StartsWith(AspectAttribute.CTX_KEY))
      {
        name = name.Remove(0, AspectAttribute.CTX_KEY.Length);
        int div = name.IndexOf(":");
        INamingContext ctx = null;
        if (div == 0)
        {
          ctx = NamingContextFactory.CreateContext();
        }
        else
        {
          string ctxName = name.Substring(0, div);
          ctx = NamingContextFactory.CreateContext(ctxName);
        }
        return ctx.Lookup(name.Substring(div + 1, name.Length - div - 1));
      }
      else
      {
        Type classType = Type.GetType(name);
        return Activator.CreateInstance(classType);
      }
    }
    private IMethodMessage Execute(IMethodMessage methodMsg, AspectConfiguration conf)
    {
      object adviseObj = this.CreateObjectForSink(conf.ComponentName);
      if(adviseObj != null)
      {
        MethodInfo[] adviseMethods = adviseObj.GetType().GetMethods();
        if(adviseMethods != null)
        {
          Hashtable argIndx = new Hashtable();
          for(int i = 0; i < methodMsg.ArgCount; i++)
          {
            argIndx.Add(methodMsg.GetArgName(i), i);
          }
          foreach(MethodInfo adviseMethod in adviseMethods)
          {
            if(adviseMethod.Name.Equals( conf.MethodName) && adviseMethod.GetParameters().Length == conf.OrderedArgs.Count)
            {
              object[] inputs = new object[conf.OrderedArgs.Count];
              bool allMatch = true;
              int cnt = 0;
              foreach(ParameterInfo para in adviseMethod.GetParameters())
              {
                string argName = conf.OrderedArgs[cnt];
                int indx = (int)argIndx[argName];
                object inputArg = methodMsg.GetArg(indx);
                if(!para.ParameterType.IsAssignableFrom(inputArg.GetType()))
                {
                  allMatch = false;
                  break;
                }
                inputs[cnt] = inputArg;
                cnt++;
              }
              if(allMatch)
              {
                adviseMethod.Invoke(adviseObj, inputs);
              }
            }
          }
        }
      }
      return methodMsg;
    }
	}
}
